# -*- coding:utf-8 -*-
from __future__ import unicode_literals

import codecs
import logging
import sys
from io import BytesIO
from threading import Lock

from django import http
from django.core import signals
from django.core.handlers import base
from django.core.urlresolvers import set_script_prefix
from django.utils import datastructures
from django.utils.encoding import force_str, force_text, iri_to_uri
from django.utils import six

# For backwards compatibility -- lots of code uses this in the wild!
from django.http.response import REASON_PHRASES as STATUS_CODE_TEXT

logger = logging.getLogger('django.request')


class LimitedStream(object):
    '''
    LimitedStream wraps another stream in order to not allow reading from it
    past specified amount of bytes.
    '''
    def __init__(self, stream, limit, buf_size=64 * 1024 * 1024):
        self.stream = stream
        self.remaining = limit
        self.buffer = b''
        self.buf_size = buf_size

    def _read_limited(self, size=None):
        if size is None or size > self.remaining:
            size = self.remaining
        if size == 0:
            return b''
        result = self.stream.read(size)
        self.remaining -= len(result)
        return result

    def read(self, size=None):
        if size is None:
            result = self.buffer + self._read_limited()
            self.buffer = b''
        elif size < len(self.buffer):
            result = self.buffer[:size]
            self.buffer = self.buffer[size:]
        else: # size >= len(self.buffer)
            result = self.buffer + self._read_limited(size - len(self.buffer))
            self.buffer = b''
        return result

    def readline(self, size=None):
        while b'\n' not in self.buffer and \
              (size is None or len(self.buffer) < size):
            if size:
                # since size is not None here, len(self.buffer) < size
                chunk = self._read_limited(size - len(self.buffer))
            else:
                chunk = self._read_limited()
            if not chunk:
                break
            self.buffer += chunk
        sio = BytesIO(self.buffer)
        if size:
            line = sio.readline(size)
        else:
            line = sio.readline()
        self.buffer = sio.read()
        return line


class WSGIRequest(http.HttpRequest):
    def __init__(self, environ):
        script_name = base.get_script_name(environ)
        path_info = base.get_path_info(environ)
        if not path_info:
            # Sometimes PATH_INFO exists, but is empty (e.g. accessing
            # the SCRIPT_NAME URL without a trailing slash). We really need to
            # operate as if they'd requested '/'. Not amazingly nice to force
            # the path like this, but should be harmless.
            path_info = '/'
        self.environ = environ
        self.path_info = path_info
        self.path = '%s/%s' % (script_name.rstrip('/'), path_info.lstrip('/'))
        self.META = environ
        self.META['PATH_INFO'] = path_info
        self.META['SCRIPT_NAME'] = script_name
        self.method = environ['REQUEST_METHOD'].upper()
        _, content_params = self._parse_content_type(self.META.get('CONTENT_TYPE', ''))
        if 'charset' in content_params:
            try:
                codecs.lookup(content_params['charset'])
            except LookupError:
                pass
            else:
                self.encoding = content_params['charset']
        self._post_parse_error = False
        try:
            content_length = int(self.environ.get('CONTENT_LENGTH'))
        except (ValueError, TypeError):
            content_length = 0
        self._stream = LimitedStream(self.environ['wsgi.input'], content_length)
        self._read_started = False
        self.resolver_match = None

    def _is_secure(self):
        return 'wsgi.url_scheme' in self.environ and self.environ['wsgi.url_scheme'] == 'https'

    def _parse_content_type(self, ctype):
        """
        Media Types parsing according to RFC 2616, section 3.7.

        Returns the data type and parameters. For example:
        Input: "text/plain; charset=iso-8859-1"
        Output: ('text/plain', {'charset': 'iso-8859-1'})
        """
        content_type, _, params = ctype.partition(';')
        content_params = {}
        for parameter in params.split(';'):
            k, _, v = parameter.strip().partition('=')
            content_params[k] = v
        return content_type, content_params

    def _get_request(self):
        if not hasattr(self, '_request'):
            self._request = datastructures.MergeDict(self.POST, self.GET)
        return self._request

    def _get_get(self):
        if not hasattr(self, '_get'):
            # The WSGI spec says 'QUERY_STRING' may be absent.
            raw_query_string = self.environ.get('QUERY_STRING', str(''))
            if six.PY3:
                raw_query_string = raw_query_string.encode('iso-8859-1').decode('utf-8')
            self._get = http.QueryDict(raw_query_string, encoding=self._encoding)
        return self._get

    def _set_get(self, get):
        self._get = get

    def _get_post(self):
        if not hasattr(self, '_post'):
            self._load_post_and_files()
        return self._post

    def _set_post(self, post):
        self._post = post

    def _get_cookies(self):
        if not hasattr(self, '_cookies'):
            raw_cookie = self.environ.get('HTTP_COOKIE', str(''))
            if six.PY3:
                raw_cookie = raw_cookie.encode('iso-8859-1').decode('utf-8')
            self._cookies = http.parse_cookie(raw_cookie)
        return self._cookies

    def _set_cookies(self, cookies):
        self._cookies = cookies

    def _get_files(self):
        if not hasattr(self, '_files'):
            self._load_post_and_files()
        return self._files

    GET = property(_get_get, _set_get)
    POST = property(_get_post, _set_post)
    COOKIES = property(_get_cookies, _set_cookies)
    FILES = property(_get_files)
    REQUEST = property(_get_request)


"""
    通信协议：利用python中的SocketServer实现一个WSGI协议。
    可调用的入口类。
    该类中实现了__call__方法，使类对象可以被调用执行
"""
class WSGIHandler(base.BaseHandler):
    initLock = Lock()                               # 类成员：锁的初始化，分配一个锁、加锁
    request_class = WSGIRequest                     # WSGIRequest类

    """
        实现__call__方法，使类型可调用；把类对象作为函数来调用
        该类实例其中被注册到django/core/servers/basehttp.py中的WSGIServer，即利用的是python中的SocketServer模块，
        具体业务就会调用到该__call__方法中来
        environ：环境变量，其中具体值从哪里来，不太清楚；
        start_response：wsgiref.simple_server.ServerHandler实例下的start_response方法
    """
    def __call__(self, environ, start_response):
        """
        根据需要加载相关的中间件。中间件只加载一次
        :param environ:
        :param start_response:
        :return:
        函数中利用了Lock前后两次判断，在保证_request_middleware不被重复初始化的提前下，最大提供性能，较少Lock性能的损耗
        """
        if self._request_middleware is None:
            with self.initLock:                             # 利用with语句实现加锁，with结束时自动解锁
                try:
                    # Check that middleware is still uninitialised.
                    if self._request_middleware is None:
                        self.load_middleware()              # 初始化加载中间件
                except:
                    # Unload whatever middleware we got
                    self._request_middleware = None
                    raise

        set_script_prefix(base.get_script_name(environ))                            # 将脚本文件的前缀设置到thread local中
        signals.request_started.send(sender=self.__class__)                         # 发送信号进行
        try:
            request = self.request_class(environ)
        except UnicodeDecodeError:
            logger.warning('Bad Request (UnicodeDecodeError)',
                exc_info=sys.exc_info(),
                extra={
                    'status_code': 400,
                }
            )
            response = http.HttpResponseBadRequest()                # 存在错误，返回错误信息
        else:
            response = self.get_response(request)                   # 进一步获取返回信息

        response._handler_class = self.__class__

        status = '%s %s' % (response.status_code, response.reason_phrase)
        response_headers = [(str(k), str(v)) for k, v in response.items()]
        for c in response.cookies.values():
            response_headers.append((str('Set-Cookie'), str(c.output(header=''))))
        start_response(force_str(status), response_headers)             # start_response(status, response_headers)
        return response             # 返回一个可迭代的结果

"""
    python中多线程的编程
    一、线程基础
        1、五种线程状态
           新建---->就绪---->运行---->死亡
                              |
                            阻塞
        2、线程同步，即锁机制
           同步机制，实现多线程对共享资源访问的保护。
        3、线程通信--条件变量
           向阻塞等待线程提供通知。
        4、线程运行和阻塞状态的转换
           导致线程阻塞的情况分为三种：
           1、等待阻塞：等待通知，收到通知时结束；
           2、同步阻塞：请求锁定导致，获取锁时继续；
           3、其他阻塞：睡眠、其他线程加入、等待IO等；
    二、Thread
        Python通过两个标准库thread和threading提供对线程的支持。其中，thread提供低级别、原始的线程以及一个简单的锁。
        1、thread
           由于thread提供的线程功能不多，无法在主线程结束后继续运行，不提供条件变量等原因，一般不用thread模块。
        2、threading
           1、概况
              锁(Lock)和条件变量(Condition)在Python中是独立的对象；
              没有优先级、线程组，线程也不能被停止、暂停、恢复和中断；
           2、相关类
              threading模块提供以下类：
              Thread、Lock、Rlock、Condition、[Bounded]Semaphore、Event、Timer、Local
           3、Thread
              Thread是线程类，有两种使用方法：1、直接传入要运行的方法；2、从Thread继承并覆盖run()方法；
              然后创建线程，并调用Thread.start()启动线程。
              Thread(group=None, target=None, name=None, args=(), kwargs={})
                group: 线程组，目前还没有实现，库引用中提示必须是None；
                target: 要执行的方法；
                name: 线程名；
                args/kwargs: 要传入方法的参数。
              isAlive()：返回线程是否在运行，即启动后、终止前；
              get/setName(name)：获取/设置线程名；
              is/setDaemon(bool)：判断/设置守护线程，初始化值从创建该线程的线程继承。没有守护线程运行，程序会终止；
              start()：启动线程；
              join([timeout])：阻塞当前上下文环境的线程，直到调用此方法的线程终止或timeout达到。
           4、Lock
              Lock(指令锁)是可用的最低级的同步指令。
              Lock处于锁定状态时，不被特定的线程拥有。
              Lock包含两种状态----锁定和非锁定
              构造方法：Lock()
              实例方法：acquire([timeout])，使线程进入同步阻塞状态，尝试获得锁定。
                       release()，释放锁，使用前线程必须已获取锁定，否则将抛出异常。
           5、RLock
              RLock(可重入锁)，可以被同一个线程请求多次的同步锁指令。
              RLock使用了"拥有的线程"和"递归等级"的概念。
              处于锁定状态时，RLock被某个线程拥有。拥有RLock的线程可以再次调用acquire()，释放锁时需要调用release()相同次数。
           6、Condition
              Condition(条件变量)通常与一个锁关联。
              需要在多个Condition中共享一个锁时，可以传递一个Lock/RLock实例给Condition的构造方法。否则，它将自己生成一个RLock实例。
              构造方法：Condition([Lock()/RLock()])
              实例方法：acquire([timeout])/release()：调用关联的锁的相应方法；
                       wait([timeout])：调用该方法使线程进入Condition的等待池，并释放锁。使用前线程必须已获取锁定，否则抛异常；
                       notify()：该方法从等待池挑选一个线程并通知，收到通知的线程自动调用acquire()尝试获取锁定；该方法不会释放锁；
                                 使用notify()之前，必须获取锁，否则抛出异常；
                       nofityAll()：通知所有线程，然后所有进程竞争获取锁资源。
                                    使用notifyAll()之前，必须获取锁，否则抛出异常。
           7、Semaphore/BoundedSemaphore
              Semaphore(信号量)，管理一个内置的计数器，每当调用acquire()时减1，调用release()时加1.
                                计数器不能小于0，为0时，acquire()将阻塞线程，直到其他线程调用release().
              应用场景：用来同步一些有"访客上限"的对象，比如：连接池。
              BoundedSemaphore，在调用release()时，检查计数器的值是否超过了计数器的初始值，如果超过了就抛出异常；
              构造方法：Semaphore(value=1)，value是计数器的初始值
              实例方法：acquire([timeout])，请求Semaphore，计数器为0时，将阻塞线程至同步阻塞状态；否则-1并立即返回；
                       release()，释放Semaphore
           8、Event
              Event(事件)是最简单的线程通信机制之一：一个线程通知事件，其他线程等待事件。
              Event内置了一个初始为False的标志，当调用set()时设为True，调用clear()时重置为False。wait()将阻塞线程至等待阻塞状态。
              构造方法：Event()
              实例方法：isSet()：当内置标志为True时，返回True；
                       set()：将标志设为True，并通知所有处于等待阻塞状态的线程恢复运行状态；
                       clear()：将标志设为False；
                       wait([timeout])：如果标志为True将立即返回，否则阻塞线程，等待其他线程调用set()
           9、Timer
              Timer(定时器)是Thread的派生类，用于在指定时间后调用一个方法。
              构造方法：Timer(interval, function, args=[], kwargs={})
                       interval：指定的时间；function：回调执行的方法；args/kwargs：回调方法的参数
           10、local
              local，用于管理thread_local(线程局部的)数据。
              对于同一个local，线程无法访问其他线程设置的属性；线程设置的属性不会被其他线程设置的同名属性替换。
              可以把local看成一个"线程-属性字典"的字典，local封装了从自身使用线程作为key检索对应"属性字典"，再使用属性名作为
              key检索属性值的细节。


    http://www.cnblogs.com/huxi/archive/2010/06/26/1765808.html
    http://www.baidu.com/link?url=MB1bK6XrsMv4YHWlarMJuZHBDd4vQ59WmpKPpunV5pEUMDigoTqDrzj90fyrvkOthLFZaMLcPZjVXSWgAknKfg-mefDOg-aSmMfi8mQLMfm&wd=&eqid=ea648a13001170e70000000355e5c2ab
"""

